# Copyright 2014 Square Inc.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

require 'base64'
require 'zlib'

# Endpoint for client libraries. Receives notifications of new exception
# occurrences and new deploys. Processes these requests.
#
# Note that this controller does not inherit from {ApplicationController}.
#
# Common
# ======
#
# All response bodies are empty.
#
# Response Codes
# --------------
#
# |          |                                   |
# |:---------|:----------------------------------|
# | 200, 201 | No errors.                        |
# | 422      | Invalid arguments.                |
# | 403      | Invalid API key.                  |
# | 404      | Unknown environment, deploy, etc. |

class Api::V1Controller < ActionController::Base
  include Squash::Ruby::ControllerMethods
  enable_squash_client except: :notify # prevent infinite loop of notifications

  rescue_from(API::UnknownAPIKeyError) { head :forbidden }
  rescue_from(API::InvalidAttributesError, ActiveRecord::RecordInvalid) do |err|
    record = err.respond_to?(:record) ? err.record : nil
    if record
      Rails.logger.error "Rejecting #{record.class}: #{record.errors.full_messages.join(' - ')}"
    else
      Rails.logger.error "Rejecting record: #{err}"
    end
    head :unprocessable_entity
  end
  rescue_from(ActiveRecord::RecordNotFound) { head :not_found }

  # API endpoint for exception notifications. Creates a new thread and
  # instantiates an {OccurrencesWorker} to process it.
  #
  # Routes
  # ------
  #
  # * `POST /api/1.0/notify`

  def notify
    BackgroundRunner.run OccurrencesWorker, request.request_parameters.to_hash
    head :ok
  end

  # API endpoint for deploy or release notifications. Creates a new {Deploy}.
  #
  # Routes
  # ------
  #
  # * `POST /api/1.0/deploy`

  def deploy
    require_params :project, :environment, :deploy

    project = Project.find_by_api_key(params['project']['api_key']) or raise(API::UnknownAPIKeyError)
    environment = project.environments.with_name(params['environment']['name']).find_or_create!(name: params['environment']['name'])

    environment.deploys.create!(deploy_params)

    head :ok
  end

  # API endpoint for uploading symbolication data. This data typically comes
  # from symbolicating scripts that run on compiled projects.
  #
  # Routes
  # ------
  #
  # * `POST /api/1.0/symbolication`

  def symbolication
    require_params :symbolications
    BackgroundRunner.run SymbolicationCreator, params.slice('symbolications')
    head :created
  end

  # API endpoint for uploading deobfuscation data. This data typically comes
  # from a renamelog.xml file generated by yGuard.
  #
  # Routes
  # ------
  #
  # * `POST /api/1.0/deobfuscation`

  def deobfuscation
    require_params :api_key, :environment, :build, :namespace
    BackgroundRunner.run ObfuscationMapCreator, params.slice('namespace', 'api_key', 'environment', 'build')
    head :created
  end

  # API endpoint for uploading source-map data. This data typically comes from
  # scripts that upload source maps generated in the process of compiling and
  # minifying JavaScript assets.
  #
  # Routes
  # ------
  #
  # * `POST /api/1.0/sourcemap`

  def sourcemap
    require_params :api_key, :environment, :revision, :sourcemap, :from, :to
    BackgroundRunner.run SourceMapCreator, params.slice('sourcemap', 'api_key', 'environment', 'revision', 'from', 'to')
    head :created
  end

  private

  def require_params(*req)
    raise(API::InvalidAttributesError, "Missing required parameter") unless req.map(&:to_s).all? { |key| params[key].present? }
  end

  def deploy_params
    params.require(:deploy).permit(:revision, :deployed_at, :hostname, :build, :version)
  end
end
